
package org.movee.spring.data.hugegraph.convert;

import com.baidu.hugegraph.structure.graph.Edge;
import com.baidu.hugegraph.structure.graph.Vertex;
import com.baidu.hugegraph.structure.gremlin.Result;
import com.baidu.hugegraph.structure.gremlin.ResultSet;
import org.movee.spring.data.hugegraph.annotation.EdgeHead;
import org.movee.spring.data.hugegraph.annotation.EdgeTail;
import org.movee.spring.data.hugegraph.mapping.HugeEntityType;
import org.movee.spring.data.hugegraph.mapping.HugeMappingContext;
import org.movee.spring.data.hugegraph.mapping.HugePersistentEntity;
import org.movee.spring.data.hugegraph.mapping.HugePersistentProperty;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.util.Assert;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * MappingGremlinConverter/MappingContext均只有一个实例
 */
@Slf4j
public class MappingHugeConverter implements HugeConverter, ApplicationContextAware {

    protected final HugeMappingContext mappingContext;
    protected ConversionService conversionService;
    private ApplicationContext applicationContext;

    public MappingHugeConverter(
            MappingContext<? extends HugePersistentEntity<?>, HugePersistentProperty> context) {

        this.mappingContext = (HugeMappingContext) context;
        this.conversionService = new DefaultConversionService();
    }

    public ApplicationContext getApplicationContext() {
        return this.applicationContext;
    }

    @Override
    public HugeMappingContext getMappingContext() {
        return this.mappingContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.applicationContext = context;
    }

    @Override
    public ConversionService getConversionService() {
        return this.conversionService;
    }

    @Override
    public <T extends Object> T read(Class<T> domainClass, @NonNull Object entityContex) {
        return null;
    }

    @Override
    public void write(@NonNull Object domain, @NonNull Object entityContex) {

    }

    public ConvertingPropertyAccessor getConvertingPropertyAccessor(@NonNull Object entity) {
        final HugePersistentEntity<?> persistentEntity = getPersistentEntity(entity.getClass());
        Assert.notNull(persistentEntity, "persistentEntity should not be null");

        final PersistentPropertyAccessor accessor = persistentEntity.getPropertyAccessor(entity);

        return new ConvertingPropertyAccessor(accessor, conversionService);
    }

    public HugePersistentEntity<?> getPersistentEntity(@NonNull Class<?> domainClass) {
        return mappingContext.getPersistentEntity(domainClass);
    }

    @Nullable
    @Override
    public Object getId(Object bean, HugePersistentEntity<?> entity) {
        return entity.getIdentifierAccessor(bean).getIdentifier();
    }

    public <T> List<T> convertResultSetToEntity(final ResultSet results, final Class<T> entityClass) {

        return Optional.ofNullable(results)
                .map(rs -> {
                    List<T> rets = new ArrayList<>();
                    Iterator<Result> itor = rs.iterator();
                    while (itor.hasNext()) {
                        log.debug("convertResultSetToEntity, {}", entityClass.getName());
                        T r = convertResultToEntity(itor.next(), null, entityClass);
                        if (r != null) {
                            rets.add(r);
                        }
                    }
                    return rets;
                })
                .orElse(new ArrayList<>());
    }

    @SuppressWarnings("unchecked")
    public <T> T convertResultToEntity(@NonNull final Result result, T entity, @NonNull final Class<T> entityClass) {

        if (result.isNull()) {
            log.debug("=====is null");
            return null;
        }


        Class objClass = result.getObject().getClass();
        log.debug("====objClass: {}", objClass.getCanonicalName());
        if (objClass == entityClass || entityClass == Object.class) {
            log.debug("objClass: {}", objClass.getCanonicalName());
            entity = (T) result.getObject();
        } else if (Vertex.class.isAssignableFrom(objClass)) {
            Vertex element = result.getVertex();
            entity = (T) convertVertexToEntity(element, entity, entityClass);
        } else if (Map.class.isAssignableFrom(objClass)) {
            entity = (T) result.getObject();
        } else if (Set.class.isAssignableFrom(objClass)) {
            entity = (T) result.getObject();
        } else if (Edge.class.isAssignableFrom(objClass)) {
            Edge element = result.getEdge();
            entity = (T) convertEdgeToEntity(element, entity, entityClass);
        }

        return entity;
    }

    @SuppressWarnings("unchecked")
    public Object convertEdgeToEntity(final Edge edge, Object entity, final Class<?> entityClass) {

        if (entity == null) {
            entity = BeanUtils.instantiateClass(entityClass);
        }

        HugePersistentEntity<?> persistentEntity = getPersistentEntity(entityClass);
        ConvertingPropertyAccessor<?> propertyAccessor = getConvertingPropertyAccessor(entity);

        Assert.state(persistentEntity.getEntityType() == HugeEntityType.EDGE,
                "this entity is not edge entity, entity: " + persistentEntity.getName());

        for (HugePersistentProperty property : persistentEntity.getPersistentProperties()) {

            if (property.isIdProperty()) {
                propertyAccessor.setProperty(property, edge.id());
            } else if (property.isAnnotationPresent(EdgeHead.class)) {
                Object outVertexInstance = propertyAccessor.getProperty(property);
                if (outVertexInstance == null) {
                    outVertexInstance = BeanUtils.instantiateClass(property.getActualType());
                    Vertex outVertex = new Vertex(edge.sourceLabel());
                    outVertex.id(edge.sourceId());
                    convertVertexToEntity(outVertex, outVertexInstance, property.getActualType());
                    propertyAccessor.setProperty(property, outVertexInstance);
                }
            } else if (property.isAnnotationPresent(EdgeTail.class)) {
                Object inVertexInstance = propertyAccessor.getProperty(property);
                if (inVertexInstance == null) {
                    inVertexInstance = BeanUtils.instantiateClass(property.getActualType());
                    Vertex inVertex = new Vertex(edge.targetLabel());
                    inVertex.id(edge.targetId());
                    convertVertexToEntity(inVertex, inVertexInstance, property.getActualType());
                    propertyAccessor.setProperty(property, inVertexInstance);
                }
            } else {
                Object value = edge.property(property.getName());
                propertyAccessor.setProperty(property, value);
            }
        }

        return entity;
    }

    @SuppressWarnings("unchecked")
    public Object convertVertexToEntity(final Vertex vertex, Object entity, final Class<?> entityClass) {

        if (entity == null) {
            entity = BeanUtils.instantiateClass(entityClass);
        }

        HugePersistentEntity<?> persistentEntity = getPersistentEntity(entityClass);
        ConvertingPropertyAccessor<?> propertyAccessor = getConvertingPropertyAccessor(entity);

        Assert.state(persistentEntity.getEntityType() == HugeEntityType.VERTEX,
                "this entity is not vertex entity, entity: " + persistentEntity.getName());

        for (HugePersistentProperty property : persistentEntity.getPersistentProperties()) {
            if (property.isIdProperty()) {
                log.debug("id: {}", vertex.id());
                propertyAccessor.setProperty(property, vertex.id());
            } else {
                Object value = vertex.property(property.getName());
                log.debug("Property name: {}, value: {}", property.getName(), value);
                propertyAccessor.setProperty(property, value);
            }
        }

        return entity;
    }


    @Override
    public <T> Vertex convertEntityToVertex(T entity, Class<T> entityClass) {

        HugePersistentEntity<?> persistentEntity = getPersistentEntity(entityClass);
        ConvertingPropertyAccessor<?> propertyAccessor = getConvertingPropertyAccessor(entity);

        Assert.state(persistentEntity.getEntityType() == HugeEntityType.VERTEX,
                "this entity is not vertex entity, entity: " + persistentEntity.getName());

        Vertex vertex = new Vertex(persistentEntity.getLabel());
        for (HugePersistentProperty property : persistentEntity.getPersistentProperties()) {
            Object value = propertyAccessor.getProperty(property);
            if (property.isIdProperty()) {
                vertex.id(value);
            } else {
                if (value != null) {
                    vertex.property(property.getName(), value);
                }
            }
        }

        return vertex;
    }

    @Override
    public <T> Edge convertEntityToEdge(T entity, Class<T> entityClass) {

        HugePersistentEntity<?> persistentEntity = getPersistentEntity(entityClass);
        ConvertingPropertyAccessor<?> propertyAccessor = getConvertingPropertyAccessor(entity);

        Assert.state(persistentEntity.getEntityType() == HugeEntityType.EDGE,
                "this entity is not edge entity, entity: " + persistentEntity.getName());

        Edge edge = new Edge(persistentEntity.getLabel());
        for (HugePersistentProperty property : persistentEntity.getPersistentProperties()) {
            if (property.isAnnotationPresent(EdgeHead.class)) {
                HugePersistentEntity<?> outVertexEntity = mappingContext.getPersistentEntity(property);
                Assert.notNull(outVertexEntity,
                        "EdgeHead entity must not be null, entity:" + persistentEntity.getName());
                Vertex outVertex = new Vertex(outVertexEntity.getLabel());
                outVertex.id(outVertexEntity.getId(propertyAccessor.getProperty(property)));
                edge.source(outVertex);
            } else if (property.isAnnotationPresent(EdgeTail.class)) {
                HugePersistentEntity<?> inVertexEntity = mappingContext.getPersistentEntity(property);
                Assert.notNull(inVertexEntity,
                        "EdgeTail entity must not be null, entity:" + persistentEntity.getName());
                Vertex inVertex = new Vertex(inVertexEntity.getLabel());
                inVertex.id(inVertexEntity.getId(propertyAccessor.getProperty(property)));
                edge.target(inVertex);
            } else if (!property.isIdProperty()){
                Object value = propertyAccessor.getProperty(property);
                if (value != null) {
                    edge.property(property.getName(), value);
                }
            }
        }

        return edge;
    }

}

